//
//  PjSip.m
//  Sip
//
//  Created by Erphan on 12/20/10.
//  Copyright 2010 Smonte. All rights reserved.
//

#import "PjSip.h"
//#import "ring.h"
//#import "SipAppDelegate.h"
#import "SipCallInfo.h"
#import "SipAccountInfo.h"
#import "SipManager.h"


#pragma mark -
#pragma mark SipCallbacks

static void postCallStateNotification(pjsua_call_id call_id, const pjsua_call_info *ci)
{
    NSAutoreleasePool *autoreleasePool = [[ NSAutoreleasePool alloc ] init];
    
	NSString *remoteInfo = @"", *remoteContact = @"";
    
	if (ci->remote_info.slen) {
		remoteInfo = [NSString stringWithUTF8String:ci->remote_info.ptr];
		NSArray *chunks = [remoteInfo componentsSeparatedByString: @"\""];
		if ([chunks count] > 1) {
			remoteInfo = [NSString stringWithFormat:@"%@", [chunks objectAtIndex:1]];
		} else if ([chunks count] == 1) {
			NSRange firstRange = [remoteInfo rangeOfString:@":"];
			NSRange secondRange;
			if (firstRange.location != NSNotFound) {
				remoteInfo = [remoteInfo substringFromIndex:firstRange.location + 1];
				secondRange=[remoteInfo rangeOfString:@"@"];
				if (secondRange.location != NSNotFound) {
					remoteInfo = [remoteInfo substringToIndex:secondRange.location];
				}
			}
		}
		//NSLog(@"RemoteINFO: %@", remoteInfo);
	}
	if (ci->remote_contact.slen)
		remoteContact = [NSString stringWithUTF8String:ci->remote_contact.ptr];
	
	SipCallInfo *sipCallInfo = [[SipCallInfo alloc] init];
	
	sipCallInfo.callId = call_id;
	sipCallInfo.role = ci->role;
	sipCallInfo.accountId = ci->acc_id;
	sipCallInfo.remoteInfo = [NSString stringWithFormat:@"%@", remoteInfo];
	sipCallInfo.remoteContact = [NSString stringWithFormat:@"%@", remoteContact];
    [sipCallInfo setState:ci->state];
	sipCallInfo.lastStatus = ci->last_status;
	sipCallInfo.mediaStatus = ci->media_status;
	sipCallInfo.configSlot = ci->conf_slot;
	sipCallInfo.connectionDuration = ci->connect_duration.sec;
	sipCallInfo.totalDuration = ci->total_duration.sec;
	
	NSDictionary *userinfo = [NSDictionary dictionaryWithObjectsAndKeys:
							  sipCallInfo, DIC_CALLINFO,
							  nil];
	[sipCallInfo release];
	
	[[NSNotificationCenter defaultCenter] postNotificationName:SIP_CALLSTATE object:nil userInfo:userinfo];
	[autoreleasePool release ];
}

/* Callback called by the library when call's state has changed */
static void on_call_state(pjsua_call_id call_id, pjsip_event *e)
{
	pjsua_call_info ci;	
	pjsua_call_get_info(call_id, &ci);
	
	PJ_LOG(1,(THIS_FILE, "Call %d state=%.*s", call_id,
			  (int)ci.state_text.slen, ci.state_text.ptr));
	
	if (ci.state == PJSIP_INV_STATE_DISCONNECTED) // Session is terminated.
	{
		// FIXME: Ring
		//SipAppDelegate *app = (SipAppDelegate *)[[UIApplication sharedApplication] delegate];
		//sip_ring_stop([app.manager getConfiguration]);
		//sip_call_deinit_tonegen(call_id);
	} 
	else if (ci.state == PJSIP_INV_STATE_EARLY) 
	{
		int code;
		//pj_str_t reason;
		pjsip_msg *msg;
		
		/* This can only occur because of TX or RX message */
		pj_assert(e->type == PJSIP_EVENT_TSX_STATE);
		
		msg = (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG ?
			   e->body.tsx_state.src.rdata->msg_info.msg :
			   e->body.tsx_state.src.tdata->msg);
		
		code = msg->line.status.code;
		//reason = msg->line.status.reason;
		
		/* Start ringback for 180 for UAC unless there's SDP in 180 */
		if (ci.role == PJSIP_ROLE_UAC && code == 180 && 
			msg->body == NULL && 
			ci.media_status == PJSUA_CALL_MEDIA_NONE) 
		{
			// FIXME: Ring
		}
	}
	
	if (ci.state != PJSIP_INV_STATE_NULL)
	{
		postCallStateNotification(call_id, &ci);
	}
}


/* Callback called by the library upon receiving incoming call */
static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id,
							 pjsip_rx_data *rdata)
{
	pjsua_call_info ci;
	
	PJ_UNUSED_ARG(acc_id);
	PJ_UNUSED_ARG(rdata);
	
	pjsua_call_get_info(call_id, &ci);
	
	PJ_LOG(1,(THIS_FILE, "Incoming call from %.*s!!",
			  (int)ci.remote_info.slen,
			  ci.remote_info.ptr));
	
	/* Automatically answer incoming calls with 180/RINGING */
	pjsua_call_answer(call_id, 180, NULL, NULL);
	postCallStateNotification(call_id, &ci);
}

/* Callback called by the library when call's media state has changed */
static void on_call_media_state(pjsua_call_id call_id)
{
    pjsua_call_info ci;
	
    pjsua_call_get_info(call_id, &ci);
		
	/* Connect ports appropriately when media status is ACTIVE or REMOTE HOLD,
	 * otherwise we should NOT connect the ports.
	 */
	if (ci.media_status == PJSUA_CALL_MEDIA_ACTIVE ||
		ci.media_status == PJSUA_CALL_MEDIA_REMOTE_HOLD) 
	{
		// When media is active, connect call to sound device.
		pjsua_conf_connect(ci.conf_slot, 0);
		pjsua_conf_connect(0, ci.conf_slot);
		
		//pjsua_conf_adjust_rx_level(0, 3.0);
		//pjsua_conf_adjust_tx_level(0, 5.0);
	}
}

static void on_reg_state(pjsua_acc_id acc_id)
{
    pjsua_acc_info acc_info;
    pj_status_t status;
	
    status = pjsua_acc_get_info(acc_id, &acc_info);
    if (status != PJ_SUCCESS)
		return;
	
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	
	SipAccountInfo *accountInfo = [[SipAccountInfo alloc] init];
	accountInfo.accountId = [NSNumber numberWithInt:acc_id];
	accountInfo.isDefault = acc_info.is_default;
	accountInfo.hasRegistration = acc_info.has_registration;
	accountInfo.expires = [NSNumber numberWithInt:acc_info.expires];
	accountInfo.status = [NSNumber numberWithInt:acc_info.status];
	accountInfo.statusText = [NSString stringWithUTF8String:acc_info.status_text.ptr];
	accountInfo.onlineStatus = [NSNumber numberWithInt:acc_info.online_status];
	
	NSDictionary *userinfo = [NSDictionary dictionaryWithObjectsAndKeys:
							  accountInfo, DIC_ACCOUNTINFO,
							  nil];
	
	[accountInfo release];

	[[NSNotificationCenter defaultCenter] postNotificationName:SIP_REGCALLBACK object:nil userInfo:userinfo];
	NSLog(@"Registration callback");
	
	[pool release];
}

static
struct my_call_data *call_init_tonegen(pjsua_call_id call_id)
{
	pj_pool_t *pool;
	struct my_call_data *cd;
	pjsua_call_info ci;
	pj_status_t status;
	
	pool = pjsua_pool_create("mycall", 512, 512);
	cd = PJ_POOL_ZALLOC_T(pool, struct my_call_data);
	cd->pool = pool;
	
	status = pjmedia_tonegen_create(cd->pool, 8000, 1, 160, 16, 0, &cd->tonegen);
	
	if (status == PJ_SUCCESS)
	{
		pjsua_conf_add_port(cd->pool, cd->tonegen, &cd->toneslot);
		
		pjsua_call_get_info(call_id, &ci);
		pjsua_conf_connect(cd->toneslot, ci.conf_slot);
		// Now keyboard plays dtmf sound on speaker/earphone
		//pjsua_conf_connect(cd->toneslot, 0); // speaker/earphone.
		
		pjsua_call_set_user_data(call_id, (void*) cd);
	}
	else
	{
		pjsua_perror(THIS_FILE, "Error: Error creating DTMF generator", status);
		pj_pool_release(pool);
		cd = NULL;
	}
	
	return cd;
}


#pragma mark -
#pragma mark private declration
@interface PjSip()
@end


@implementation PjSip

#pragma mark -
#pragma mark Implementation

@synthesize sipUserName, sipPassword, sipDomain;

- (id)initWithManager:(SipManager *)_manager {
    self = [super init];
	if (self) {
		manager = _manager;
	}
	return self;
}

- (void)dealloc {
	pjsua_destroy();
	[super dealloc];
}

- (void)manageCodecs {
	const pj_str_t ID_ALL = {"*", 1};
	pj_str_t codec = {NULL, 0};
	
	pjsua_codec_set_priority(&ID_ALL, PJMEDIA_CODEC_PRIO_DISABLED);
    
	codec = pj_str("PCMA");
	pjsua_codec_set_priority(&codec, PJMEDIA_CODEC_PRIO_NEXT_HIGHER);
	
	codec = pj_str("PCMU");
	pjsua_codec_set_priority(&codec, PJMEDIA_CODEC_PRIO_NEXT_HIGHER);
	
	codec = pj_str("AMR");
	pjsua_codec_set_priority(&codec, PJMEDIA_CODEC_PRIO_NORMAL);
	
	codec = pj_str("gsm");
	pjsua_codec_set_priority(&codec, PJMEDIA_CODEC_PRIO_NORMAL);
	
	codec = pj_str("G729");
	pjsua_codec_set_priority(&codec, PJMEDIA_CODEC_PRIO_DISABLED);

//	pjsua_codec_set_priority(&ID_ALL, PJMEDIA_CODEC_PRIO_DISABLED);
//	codec = pj_str("PCMA");
//	status = pjsua_codec_set_priority(&codec, PJMEDIA_CODEC_PRIO_DISABLED);
//	
//	codec = pj_str("PCMU");
//	status = pjsua_codec_set_priority(&codec, PJMEDIA_CODEC_PRIO_DISABLED);
//	
//	codec = pj_str("AMR");
//	status = pjsua_codec_set_priority(&codec, PJMEDIA_CODEC_PRIO_DISABLED);
//	
//	codec = pj_str("iLBC");
//	status = pjsua_codec_set_priority(&codec, PJMEDIA_CODEC_PRIO_NEXT_HIGHER);
//	
//	codec = pj_str("G729");
//	status = pjsua_codec_set_priority(&codec, PJMEDIA_CODEC_PRIO_DISABLED);
	
}

- (BOOL)initializePJ {
	
	app_config_struct *app_config = [manager getConfiguration];
	
	pj_status_t status;
	
	status = pjsua_create();
	if (status != PJ_SUCCESS) {
		NSLog(@"Error in pjsua_create(): %d", status);
		goto error;
	}
	
	if ([sipDomain length] == 0) {
		NSLog(@"Invalid domain name provided");
		goto error;
	}
	
	const char *c_sipDomain = [[NSString stringWithFormat:@"sip:%@", sipDomain] UTF8String];
	status = pjsua_verify_sip_url(c_sipDomain);
	if (status != PJ_SUCCESS) {
		NSLog(@"Unable to verify sip url");
		goto error;
	}
		
	pjsua_config_default(&app_config->cfg);
	
	// callbacks
	app_config->cfg.cb.on_call_state = &on_call_state;
	app_config->cfg.cb.on_reg_state = &on_reg_state;
	app_config->cfg.cb.on_call_media_state = &on_call_media_state;
	app_config->cfg.cb.on_incoming_call = &on_incoming_call;

	// logging level
	pjsua_logging_config_default(&app_config->log_cfg);
	app_config->log_cfg.console_level = SIP_LOGGING_LEVEL;

	status = pjsua_init(&app_config->cfg, &app_config->log_cfg, NULL);
	if (status != PJ_SUCCESS) {
		NSLog(@"Error in pjsua_init() -> %d", status);
		goto error;
	}
	
	// media configuration
	pjsua_media_config_default(&(app_config->media_cfg));
	app_config->media_cfg.clock_rate = 16000;
	app_config->media_cfg.snd_clock_rate = 16000;
	app_config->media_cfg.snd_auto_close_time = 0;
	app_config->media_cfg.ec_tail_len = 256;
	app_config->media_cfg.ec_options = PJMEDIA_ECHO_SIMPLE;
	
	// add transport
	pjsua_transport_config_default(&app_config->transport_cfg);
	app_config->transport_cfg.port = SIP_PORT;
	
	status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &app_config->transport_cfg, NULL);
	if (status != PJ_SUCCESS) {
		NSLog(@"Error creating transport -> %d", status);
		goto error;
	}
	
	pjsua_transport_config_default(&app_config->rtp_cfg);
	app_config->rtp_cfg.port = 4000;
	status = pjsua_media_transports_create(&app_config->rtp_cfg);
	
	
	[self manageCodecs];
	
	if (status != PJ_SUCCESS) {
		NSLog(@"Error creating RTP transport -> %d", status);
		goto error;
	}
	
	//start pjsua now
	status = pjsua_start();
	
	if (status != PJ_SUCCESS) {
		NSLog(@"Error starting pjsua -> %d", status);
		goto error;
	}


	return YES;
error:
	pjsua_destroy();
	return NO;
	
}

- (BOOL)removeAccount {
	if (pjsua_acc_get_count() == 0)
		return YES;
	
	pj_status_t status = pjsua_acc_del(acc_id);
	if (status == PJ_SUCCESS)
		return YES;
	
	return NO;
}

- (void)disconnect {
	//pjsua_call_hangup_all();
	[self removeAccount];
	pjsua_destroy();
}

- (BOOL)connect {
	if (pjsua_acc_get_count() > 0)
		return NO;
	
	pjsua_acc_config acc_cfg;
	//pjsua_acc_id acc_id;
	
	pjsua_acc_config_default(&acc_cfg);
		
	NSString *sipServerUri = [NSString stringWithFormat:@"sip:%@@%@", sipUserName, sipDomain];
	NSLog(@"SipUri: %@", sipServerUri);
	const char *cSipServerUri = [sipServerUri UTF8String];
	acc_cfg.id = pj_str((char*)cSipServerUri);
	NSString *_sipDomain = [NSString stringWithFormat:@"sip:%@", sipDomain];
	const char *c_sipDomain = [_sipDomain UTF8String];
	acc_cfg.reg_uri = pj_str((char*)c_sipDomain);
	acc_cfg.cred_count = 1;
	acc_cfg.cred_info[0].realm = pj_str("*");
	acc_cfg.cred_info[0].scheme = pj_str("digest");
	acc_cfg.cred_info[0].username = pj_str((char*)[sipUserName UTF8String]);
	acc_cfg.cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
	acc_cfg.cred_info[0].data = pj_str((char*)[sipPassword UTF8String]);
	
	acc_cfg.reg_timeout = 3600;
	pj_status_t status = pjsua_acc_add(&acc_cfg, PJ_TRUE, &acc_id);
	
	if (status != PJ_SUCCESS) {
		NSLog(@"Error adding account -> %d", status);
		return YES;
	}
	return NO;
}

- (BOOL)makeCall:(NSString *)number :(int *)callId{
	char uri[256];
	const char *sip_domain;
	
	sip_domain = [sipDomain UTF8String];
	
	pjsua_call_id call_id;
	
	pj_ansi_snprintf(uri, 256, "sip:%s@%s", [number UTF8String], sip_domain);
	PJ_LOG(5,(THIS_FILE,  "Calling URI \"%s\".", uri));
	
	pj_status_t status = PJ_SUCCESS;
	pj_str_t pj_uri;
	
	status = pjsua_verify_sip_url(uri);
	if (status != PJ_SUCCESS) {
		NSLog(@"Invalid Caller URI");
		UIAlertView *alert = [[UIAlertView alloc] initWithTitle:APP_NAME message:@"Invalid Called ID, Please dial in international format" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
		[alert show];
		[alert release];
		return NO;
	}
	
	pj_uri = pj_str((char *)uri);
	
	status = pjsua_call_make_call(acc_id, &pj_uri, 0, NULL, NULL, &call_id);

	if (status == PJ_SUCCESS) {
		*callId = call_id;
		return YES;
	}
	return NO;
}

- (BOOL)answerCall:(int)callId {
	pj_status_t status;
	pjsua_call_id call_id = callId;
	status = pjsua_call_answer(call_id, 200, NULL, NULL);
	if (status != PJ_SUCCESS)
	{
		return NO;
	}
	return YES;	
}

- (BOOL)hangup:(int)callId {
	pj_status_t status = PJ_SUCCESS;
	
	pjsua_call_id call_id = callId;
	
	status = pjsua_call_hangup(call_id, 0, NULL, NULL);
	if (status == PJ_SUCCESS)
		return YES;
	
	return NO;
}

- (BOOL)declineCall:(int)callId {
    pj_status_t status = PJ_SUCCESS;
	
	pjsua_call_id call_id = callId;
	
	status = pjsua_call_hangup(call_id, 603, NULL, NULL);
	if (status == PJ_SUCCESS)
		return YES;
	
	return NO;
}

- (BOOL)playDTMF:(NSNumber *)callId :(pj_str_t *)digits
{
	pj_status_t status;
	int call_id = [callId intValue];
	
	status = pjsua_call_dial_dtmf(call_id, digits);
	
	if (status == PJMEDIA_RTP_EREMNORFC2833)
	{    
		pjmedia_tone_digit *d;
		struct my_call_data *cd;
		pj_uint32_t i;
		
		cd = (struct my_call_data*) pjsua_call_get_user_data(call_id);
		if (cd == NULL)
		{
			cd = call_init_tonegen(call_id);
			if (cd == NULL)
				return NO;
		}
		
		d = pj_pool_calloc (cd->pool, digits->slen, sizeof(pjmedia_tone_digit));
		
		for (i = 0; i < digits->slen; ++i)
		{
			d[i].digit = digits->ptr[i];
			d[i].on_msec = 100;
			d[i].off_msec = 100;
			d[i].volume = 16383;
		}
		pjmedia_tonegen_stop(cd->tonegen);
		pjmedia_tonegen_play_digits(cd->tonegen, digits->slen, d, 0);
	}

	return YES;
}

- (BOOL)setMute:(BOOL)enable
{
	pj_status_t status;
	if (enable)
		status = pjsua_conf_adjust_rx_level(0 /* pjsua_conf_port_id slot*/, 0.0f);
	else
		status = pjsua_conf_adjust_rx_level(0 /* pjsua_conf_port_id slot*/, 1.0f);
	
	if (status == PJ_SUCCESS) {
		return YES;
	}
	return NO;
}

- (BOOL)setHoldEnabled: (BOOL)enable :(int)_call_id;
{
    if (_call_id == PJSUA_INVALID_ID)
        return NO;
    
	pj_status_t status;
	if (enable)
	{
        status = pjsua_call_set_hold(_call_id, NULL);
	}
	else
	{
        status = pjsua_call_reinvite(_call_id, PJ_TRUE, NULL);
	}
	
	if (PJ_SUCCESS == status) {
		return YES;
	}
	
	return NO;
}

- (BOOL)setSpeakerPhoneEnabled:(BOOL)enable
{
	UInt32 route;
	route = enable ? kAudioSessionOverrideAudioRoute_Speaker : 
	kAudioSessionOverrideAudioRoute_None;
	OSStatus status = AudioSessionSetProperty (kAudioSessionProperty_OverrideAudioRoute, 
							 sizeof(route), &route);
	if (status == kAudioSessionNoError) {
		return YES;
	}
	return NO;
}

- (void)showRegistrationStatus {
	if (pjsua_acc_get_count() == 0) {
		UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:APP_NAME message:@"Timeout occured" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] autorelease];
		[alert show];
		return;
	}
	
	pjsua_acc_info acc_info;
    pj_status_t status;
	
    status = pjsua_acc_get_info(acc_id, &acc_info);
    if (status != PJ_SUCCESS)
		return;
	NSString *strMessage = [NSString stringWithUTF8String:acc_info.status_text.ptr];
	
	if ([strMessage length]> 0) {
		UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:APP_NAME message:strMessage delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] autorelease];
		[alert show];
	}
}

- (int)getSipStatus {
	if (pjsua_acc_get_count() == 0) {
		return 408;
	}
	pjsua_acc_info acc_info;
    pj_status_t status;
	
    status = pjsua_acc_get_info(acc_id, &acc_info);
    if (status != PJ_SUCCESS)
		return 0;
	return acc_info.status;
}

#pragma mark -
#pragma mark private implementation
@end
